<template>
    <section
      :style="{ ...styles.box, ...boxStyle }"
      class="v-screen-box"
      ref="box"
    >
      <div
        :style="{ ...styles.wrapper, ...wrapperStyle }"
        class="screen-wrapper"
        ref="screenWrapper"
      >
        <slot></slot>
      </div>
    </section>
  </template>
  <script lang="ts" setup>
  import { nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
  import type { CSSProperties, PropType } from "vue";
  /**
   * 防抖函数
   * @param {Function} fn
   * @param {number} delay
   * @returns {() => void}
   */
  function debounce(fn: Function, delay: number): () => void {
    // let timer: NodeJS.Timer;
    let timer: any;
    return function (...args: any[]): void {
      if (timer) clearTimeout(timer);
      timer = setTimeout(
        () => {
          typeof fn === "function" && fn.apply(null, args);
          clearTimeout(timer);
        },
        delay > 0 ? delay : 100
      );
    };
  }
  
  interface IState {
    originalWidth: string | number;
    originalHeight: string | number;
    width?: string | number;
    height?: string | number;
    observer: null | MutationObserver;
  }
  type IAutoScale =
    | boolean
    | {
        x?: boolean;
        y?: boolean;
      };
  const props = defineProps({
    width: {
      type: [String, Number] as PropType<string | number>,
      default: 1920,
    },
    height: {
      type: [String, Number] as PropType<string | number>,
      default: 1080,
    },
    fullScreen: {
      type: Boolean as PropType<boolean>,
      default: true,
    },
    autoScale: {
      type: [Object, Boolean] as PropType<IAutoScale>,
      default: true,
    },
    delay: {
      type: Number as PropType<number>,
      default: 500,
    },
    boxStyle: {
      type: Object as PropType<CSSProperties>,
      default: () => ({}),
    },
    wrapperStyle: {
      type: Object as PropType<CSSProperties>,
      default: () => ({}),
    },
  });
  
  const state = reactive<IState>({
    width: 0,
    height: 0,
    originalWidth: 0,
    originalHeight: 0,
    observer: null,
  });
  
  const styles: Record<string, CSSProperties> = {
    box: {
      overflow: "hidden",
      backgroundSize: `100% 100%`,
      background: `#000`,
      width: `100vw`,
      height: `100vh`,
    },
    wrapper: {
      transitionProperty: `all`,
      transitionTimingFunction: `cubic-bezier(0.4, 0, 0.2, 1)`,
      transitionDuration: `500ms`,
      position: `relative`,
      overflow: `hidden`,
      zIndex: 100,
      transformOrigin: `left top`,
      display: 'flex',
      flexDirection: 'column',
    },
  };
  
  const screenWrapper = ref<HTMLElement>();
  const box = ref<HTMLElement>();
  
  watch(
    () => props.autoScale,
    async (newVal: any) => {
      if (newVal) {
        onResize();
        addListener();
      } else {
        clearListener();
        clearScreenWrapperStyle();
      }
    }
  );
  /**
   * 初始化大屏容器宽高
   */
  const initSize = () => {
    return new Promise<void>((resolve) => {
      box.value!.scrollLeft = 0;
      box.value!.scrollTop = 0;
      nextTick(() => {
        // region 获取大屏真实尺寸
        if (props.width && props.height) {
          state.width = props.width;
          state.height = props.height;
        } else {
          state.width = screenWrapper.value?.clientWidth;
          state.height = screenWrapper.value?.clientHeight;
        }
        // endregion
  
        // region 获取画布尺寸
        if (!state.originalHeight || !state.originalWidth) {
          state.originalWidth = window.screen.width;
          state.originalHeight = window.screen.height;
        }
        // endregion
        resolve();
      });
    });
  };
  
  /**
   * 更新大屏容器宽高
   */
  const updateSize = () => {
    if (state.width && state.height) {
      screenWrapper.value!.style.width = `${state.width}px`;
      screenWrapper.value!.style.height = `${state.height}px`;
    } else {
      screenWrapper.value!.style.width = `${state.originalWidth}px`;
      screenWrapper.value!.style.height = `${state.originalHeight}px`;
    }
  };
  const clearScreenWrapperStyle = () => {
    screenWrapper.value!.style.transform = "";
    screenWrapper.value!.style.margin = "";
  };
  const autoScale = (scale: number) => {
    if (!props.autoScale) {
      return;
    }
    const domWidth = screenWrapper.value!.clientWidth;
    const domHeight = screenWrapper.value!.clientHeight;
    const currentWidth = document.body.clientWidth;
    const currentHeight = document.body.clientHeight;
    screenWrapper.value!.style.transform = `scale(${scale},${scale})`;
    let mx = Math.max((currentWidth - domWidth * scale) / 2, 0);
    let my = Math.max((currentHeight - domHeight * scale) / 2, 0);
    if (typeof props.autoScale === "object") {
      !props?.autoScale?.x && (mx = 0);
      !props?.autoScale?.y && (my = 0);
    }
    screenWrapper.value!.style.margin = `${my}px ${mx}px`;
  };
  const updateScale = () => {
    // 获取真实视口尺寸
    const currentWidth = document.body.clientWidth;
    const currentHeight = document.body.clientHeight;
    // 获取大屏最终的宽高
    const realWidth = state.width || state.originalWidth;
    const realHeight = state.height || state.originalHeight;
    // 计算缩放比例
    const widthScale = currentWidth / +realWidth;
    const heightScale = currentHeight / +realHeight;
    // 若要铺满全屏，则按照各自比例缩放
    if (props.fullScreen) {
      screenWrapper.value!.style.transform = `scale(${widthScale},${heightScale})`;
      return false;
    }
    // 按照宽高最小比例进行缩放
    const scale = Math.min(widthScale, heightScale);
    autoScale(scale);
  };
  
  const onResize = debounce(async () => {
    await initSize();
    updateSize();
    updateScale();
  }, props.delay);

  const initMutationObserver = () => {
    const observer = (state.observer = new MutationObserver(() => {
      onResize();
    }));
    observer.observe(screenWrapper.value!, {
      attributes: true,
      attributeFilter: ["style"],
      attributeOldValue: true,
    });
  };
  
  const clearListener = () => {
    window.removeEventListener("resize", onResize);
    // state.observer?.disconnect();
  };
  
  const addListener = () => {
    window.addEventListener("resize", onResize);
    // initMutationObserver();
  };

  onMounted(() => {
    nextTick(async () => {
      await initSize();
      updateSize();
      updateScale();
      addListener();
      // initMutationObserver();
    });
  });

  onUnmounted(() => {
    clearListener();
    // state.observer?.disconnect();
  });
  </script>
  